<?php
/*
 *  This file is part of Urd.
 *
 *  Urd is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *  Urd is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. See the file "COPYING". If it does not
 *  exist, see <http://www.gnu.org/licenses/>.
 *
 * $LastChangedDate: 2008-12-21 19:45:17 +0100 (Sun, 21 Dec 2008) $
 * $Rev: 355 $
 * $Author: gavinspearhead $
 * $Id: urdd_client.php 355 2008-12-21 18:45:17Z gavinspearhead $
 */


// This is an include-only file:
if (!defined('ORIGINAL_PAGE')) die('This file cannot be accessed directly.');

$pathuc = realpath(dirname(__FILE__));


require_once $pathuc . '/urdd_command.php';
require_once $pathuc . '/urdd_protocol.php';
require_once $pathuc . '/../config.php';
require_once $pathuc . '/../functions/defines.php';
require_once $pathuc . '/../functions/libs/socket.php';
require_once $pathuc . '/../functions/autoincludes.php';


function get_username_password(DatabaseConnection $db, $userid)
{
    assert(is_numeric($userid));
    $db->escape($userid, TRUE);
    $res = $db->execute_query("SELECT \"name\", \"pass\" FROM users WHERE \"ID\" = $userid");
    if ($res == FALSE || empty($res)) {
        var_dump(trace_str(debug_backtrace()));
        throw new exception("Invalid userID $userid given in urdd_client constructor", ERR_INVALID_USERNAME);
    }
    return array ($res[0]['name'], $res[0]['pass']);
}


class urdd_client {
    private $connected;
    private $hostname;
    private $port;
    private $sock;
    private $username;
    private $password;
    private $lastcode;
    private $lastmsg;
    private $timeout;
    private $can_connect;

    function __construct ($hostname, $port, $userid = 0, $timeout=DEFAULT_SOCKET_TIMEOUT)
    {
        assert(is_numeric($port) && is_numeric($userid) && is_numeric($timeout));
        global $db;
        if ($userid > 0) {
            list($username , $md5pass) = get_username_password($db, $userid);
            $password = 'hash:' . $md5pass;

            $this->lastcode = 0;
            $this->lastmsg = '';
            $this->timeout = (int) $timeout;
            $this->cleanup() ;
            try {
                $this->connect($hostname, $port, $username, $password, $timeout);
                $this->connected = TRUE;
            } catch (exception $e) {
                $this->cleanup();
            }
        }
        else {
            $this->can_connect = FALSE;
            $this->quick_connect($hostname, $port, $timeout);
        }
    }
    private function quick_connect($hostname, $port, $timeout)
    {
        $rv = FALSE;
        try {
            $this->sock = new socket();
            $this->sock->connect($hostname, $port, FALSE, $timeout);
            $rv = $this->sock->readLine();
        } catch (exception $e) {
            $this->cleanup();
        }
        if (substr($rv, 0, 3) == '299')
            $this->can_connect = TRUE;
        $this->disconnect();
        $this->cleanup();
    }
    function can_connect()
    {
        return $this->can_connect;
    }
    private function cleanup() 
    { 
        $this->sock = NULL; 
        $this->connected = FALSE;
    }
    function __destruct () 
    {
        $this->disconnect();
    }
    function is_connected() 
    { 
        return $this->connected;
    }
    function get_lastcode() 
    {
        return $this->lastcode;
    }
    function get_lastmsg() 
    { 
        return $this->lastmsg;
    }
    function connect ($hostname, $port=URDD_PORT, $username='', $password='', $timeout=DEFAULT_SOCKET_TIMEOUT)
    {
        assert(is_numeric($port) && is_numeric($timeout));
        global $config, $prefs;
        $this->hostname = $hostname;
        $this->port = (int) $port;
        $this->username = $username;
        $this->password = $password;
        $this->timeout = (int) $timeout;
        $this->sock = new socket();
        $this->sock->connect($hostname, $port, FALSE, $timeout);
        $rv = $this->sock->readLine();
        if ($rv === FALSE) {
            $this->cleanup();
            throw new exception ('Cannot read from socket', ERR_SOCKET_FAILURE);
        }
        $this->connected = TRUE;
        if ($this->username !== '' && $this->password !== '') {
            list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_USER) . ' ' . $this->username);
            if ($code != 331) {
                $this->lastcode = $code;
                $this->lastmsg = $resp;
                $this->sock->disconnect();
                $this->cleanup();
                write_log('Wrong username and password');
                throw new exception('Wrong username and password', ERR_ACCESS_DENIED);
            }
            list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_PASS) . ' ' . $this->password);
            if ($code != 240) {
                $this->lastcode = $code;
                $this->lastmsg = $resp;
                $this->sock->disconnect();
                $this->cleanup();
                write_log('Wrong username and password');
                throw new exception('Wrong username and password', ERR_ACCESS_DENIED);
            }
        } else
            throw new exception ('Needs a username and password', ERR_NOT_LOGGED_IN);
        $this->connected = TRUE;
 
    }
    protected function split_response ($lines)
    {
        $code = (int)(substr($lines[0], 0, 3));
        if ($code > 999 || $code < 100) {
            throw new exception("Incorrect response $code", ERR_INVALID_RESPONSE);
        }
        $text = substr($lines[0], 4);
        if (($code % 100) > 50)
            $data = array_slice($lines, 1);
        else 
            $data = NULL;
        return array($code, $text, $data);
    }
    protected function send_multi_command($cmd)
    {
        // Check if we have a connection:
        if ($this->sock == NULL) 
            $this->connect($this->hostname, $this->port, $this->username, $this->password, $this->timeout);

        // Send command:
        $rv = $this->sock->writeLine($cmd);
        if($rv === FALSE) { // can't write it somehow, cleanup and quit
            $this->sock->disconnect();
            $this->cleanup();
            throw new exception('Could not write to socket', ERR_SOCKET_FAILURE); 
        }
        $timeout = time() + 10;
        // Read reply:
        while (1) {    
            // Read the result
            $line = $this->sock->readLine();
            if ($line === FALSE || time() > ($timeout)) { // if we have a timeout or readline timesout
                $this->disconnect();
                $this->cleanup();
                throw new exception('waited too long', ERR_WAITED_TOO_LONG);
            }

            $bufferlines[] = $line;

            if (preg_match("/^([0-9])([0-9])([0-9]) (.+)$/", $bufferlines[0], $res)) {
                // Only if the second value is 5 or more, there is a multi-line response
                if ($res[2] < 5) 
                    break;

                if ($bufferlines[count($bufferlines)-1] == '.') {
                    unset ($bufferlines[count($bufferlines)-1]); // remove the line with the dot
                    break;
                }
            }
        }

        list ($code, $resp, $data) = $this->split_response($bufferlines);
        $this->lastcode = $code;
        $this->lastmsg = $resp;
        return array ($code, $resp, $data);  
    }
    function decode($string)
    {
        // $string can be "(6) [1] [2] [3] Download scheduled.", returning the right values:
        $return = array();

        if (preg_match("/^\(([0-9]+)\)/", $string, $res)) 
            $return[] = $res[1];
        else 
            $return[] = array();
        
        if (preg_match_all("|\[([^[]+)\]|U", $string, $res)) 
            $return[] = $res[1];
        else 
            $return[] = array();
    
        return $return;
    }
    function gensets($msg_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_GENSETS) . ' ' . $msg_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function update($msg_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_UPDATE) . ' ' . $msg_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }

    function update_rss($feed_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_UPDATE_RSS) . ' ' . $feed_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function create_preview()
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DOWNLOAD) . ' preview');
        if ($code == 210)
            return $resp;
        else 
            return FALSE;
    }

    function create_download()
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DOWNLOAD));
        if ($code == 210)
            return $resp;
        else 
            return FALSE;
    }
    function make_nzb()
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_MAKE_NZB));
        if ($code == 210)
            return $resp;
        else 
            return FALSE;
    }
    function cancel($msg_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_CANCEL) . ' ' . $msg_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function continue_cmd($msg_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_CONTINUE) . ' ' . $msg_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function pause($msg_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_PAUSE) . ' '  . $msg_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function cleandb($arg='')
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_CLEANDB). '  ' . $arg);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function expire($msg_id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_EXPIRE) . ' ' . $msg_id);
        if ($code == 201 || $code == 202)
            return TRUE;
        else 
            return FALSE;
    }

    function expire_rss($feed_id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_EXPIRE_RSS) . ' ' . $feed_id);
        if ($code == 201 || $code == 202)
            return TRUE;
        else 
            return FALSE;
    }
    function stop($msg_id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_STOP) .' ' . $msg_id);
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function purge($msg_id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_PURGE) . ' ' . $msg_id);
        if ($code == 201 || $code == 202)
            return TRUE;
        else 
            return FALSE;
    }

    function purge_rss($feed_id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_PURGE_RSS) . ' ' . $feed_id);
        if ($code == 201 || $code == 202)
            return TRUE;
        else 
            return FALSE;
    }
    function preempt($msg_start, $msg_stop = '')
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_PREEMPT) .' ' . $msg_start . ' ' . (($msg_stop !== NULL)? $msg_stop : '')); 
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function unschedule($cmd, $arg)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command (COMMAND_UNSCHEDULE) . " $cmd $arg");
        if ($code == 201 || $code == 202)
            return TRUE;
        else 
            return FALSE;
    }
    function schedule($cmd, $arg, $timestamp, $recurrence=NULL)
    {
        if ($recurrence !== NULL)
            $rec_str = " # $recurrence ";
        else
            $rec_str = '';
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SCHEDULE) . " $cmd $arg @ $timestamp $rec_str");
        if ($code == 201 || $code == 202)
            return TRUE;
        else 
            return FALSE;
    }
    function show($var = 'threads')
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SHOW) . " $var");
        if ($code == 253)
            return $data;
        else
            return FALSE;   
    }
    function diskfree($format = 'b')
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DISKFREE) . " $format");
        if ($code == 256) {
            $res = explode(' ', $data[0]);
            return array($res[0], $res[1], $res[3], $res[4]);
        } else if ($code == 263) {
            $res = explode(' ', $data[0]);
            return $res[0]; 
        } else 
            return FALSE;
    }
    function time()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_TIME));
        if ($code == 260) {
            return $data[0];
        } else 
            return FALSE;
    }
    function uptime()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_UPTIME));
        if ($code == 259) {
            return $data[0];
        } else 
            return FALSE;
    }
    function update_newsgroups()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_GROUPS));
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function group($groupID)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_GROUP) . " $groupID");
        if ($code == 258)
            return $data;
        else 
            return FALSE;
    }
    function subscribe($groupID, $expire)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SUBSCRIBE) . " $groupID on $expire");
        if ($code == 201)
            return $data;
        else 
            return FALSE;
    }
    function subscribe_rss($feed_id, $expire)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SUBSCRIBE_RSS) . " $feed_id on $expire");
        if ($code == 201)
            return $data;
        else 
            return FALSE;
    }
    function unsubscribe($groupID)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SUBSCRIBE) . " $groupID off");
        if ($code == 201)
            return $data;
        else 
            return FALSE;
    }
    function unsubscribe_rss($feed_id)
    {
        list ($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SUBSCRIBE_RSS) . " $feed_id off");
        if ($code == 201)
            return $data;
        else 
            return FALSE;
    }
    function optimise()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_OPTIMISE));
        if ($code == 231)
            return TRUE;
        else 
            return FALSE;
    }
    function noop()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_NOOP));
        if ($code == 231)
            return TRUE;
        else 
            return FALSE;
    }
    function echo_cmd($msg)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_ECHO) . " $msg");
        if ($code == 200)
            return ;
        else 
            return FALSE;
    }
    function version()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_VERSION));
        if ($code == 262)
            return $data;
        else 
            return FALSE;
    }

    function status()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_STATUS));
        if ($code == 251)
            return $data;
        else 
            return FALSE;
    }
    function unpause($id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_CONTINUE) . " $id");
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function cleandir($dir)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_CLEANDIR) . " $dir");
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function shutdown()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SHUTDOWN));
        $this->disconnect();
        if ($code == 222)
            return TRUE;
        else 
            return FALSE;
    }
    function set($parameter, $value1, $value2='', $value3='')
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SET) . " $parameter $value1 $value2 $value3");
        if ($code == 200)
            return TRUE;
        else 
            return FALSE;
    }
    function check_version()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_CHECK_VERSION)) ;
        if ($code == 201)
            return TRUE;
        else 
            return FALSE;
    }
    function sendsetinfo()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_SENDSETINFO)) ;
        if ($code == 201)
            return TRUE;
        else 
            return FALSE;
    }

    function getsetinfo()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_GETSETINFO));
        if ($code == 201)
            return TRUE;
        else 
            return FALSE;
    }

    function disconnect()
    {
        if ($this->sock !== NULL && $this->connected == TRUE && is_resource($this->sock)) {
            $sb = array($this->sock);
            $null_array = array();
            $rv = @socket_select($null_array, $sb, $null_array, 0); // check if socket is writable and will not block
            if ($rv > 0) // if so we will send a quit command
                $this->send_multi_command('quit');
            // otherwise we will just close the connection
            @socket_close($this->sock);
        }
        $this->cleanup();
    }
    function move_cmd($direction, $command, $id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_MOVE) . " $direction $command $id");
        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function add_set_data($dlid, $setid, $preview=FALSE) 
    {
        $preview_str = ($preview === TRUE) ? 'preview' : '';
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_ADDDATA) . " $dlid set $setid $preview_str");
        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function add_bin_data($dlid, $groupid, $binid, $preview=FALSE) 
    {
        $preview_str = ($preview === TRUE) ? 'preview' : '';
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_ADDDATA) . " $dlid binary $groupid $binid $preview_str");
        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function parse_nzb($url, $dlid='', $start_time=NULL)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_PARSE_NZB) . " $dlid '$url' $start_time");
        if ($code == 201)
            return TRUE;
        else
            return FALSE;
    }
    function merge_sets($setid1, array $setid_list)
    {
        $setid2 = '';
        foreach ($setid_list as $s)
            $setid2 .= $s . ' '; 
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_MERGE_SETS) . " {$setid1} {$setid2}");
        if ($code == 201)
            return TRUE;
        else
            return FALSE;
    }
   function restart_urdd()
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_RESTART));
        if ($code == 223)
            return TRUE;
        else
            return FALSE;
    }
    function findservers($options= '')
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_FINDSERVERS) . " $options");
        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function delete_set(array $setid)
    {
        $setid_list = '';
        if ($setid == array()) 
            return FALSE;
        $cnt = 0;
        foreach ($setid as $s) {
            $setid_list .= " $s";
            $cnt++;
            if ($cnt > 10) {
                list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DELETE_SET) . " $setid_list");
                $cnt = 0;
                $setid_list = '';

            }
        }
        if ($cnt > 0)
                list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DELETE_SET) . " $setid_list");

        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function delete_set_rss(array $setid)
    {
        $setid_list = '';
        if ($setid == array()) 
            return FALSE;
        $cnt = 0;
        foreach ($setid as $s) {
            $setid_list .= " $s";
            $cnt++;
            if ($cnt > 10) {
                list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DELETE_SET_RSS) . " $setid_list");
                $cnt = 0;
                $setid_list = '';
            }
        }
        if ($cnt > 0)
            list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_DELETE_SET_RSS) . " $setid_list");
        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function unpar_unrar($dlid='')
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_UNPAR_UNRAR) . " $dlid");
        if ($code == 200)
            return TRUE;
        else
            return FALSE;
    }
    function post($id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_POST) . " $id");
        if ($code == 201)
            return TRUE;
        else
            return FALSE;
    }
    function post_message($id)
    {
        list($code, $resp, $data) = $this->send_multi_command(get_command(COMMAND_POST_MESSAGE) . " $id");
        if ($code == 201)
            return TRUE;
        else
            return FALSE;
    }


}


?>
